#include "ljsonc_parse.h"
#include "ljsonc_create.h"
#include "str_buffer.h"

#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

int error_parse = 0;

static ljsonc_json *precess_json(const char **addr_str);
static ljsonc_element *precess_element(const char **addr_str);
static CString precess_string(const char **addr_str);
static ljsonc_array *precess_array(const char **addr_str);
static ljsonc_value *precess_value(const char **addr_str);
static void tidy_string(char *des, const char *src);

ljsonc_json *parse_from_string(const char *c_str)
{
    ljsonc_json *json = NULL;
    error_parse = 0;
    
    if (c_str) {
        json = create_json();
        
        if (json) {
            const char *p_cur = strchr(c_str, '{');
            ++p_cur;
            
            if (p_cur) {
                while (*p_cur != '}' && *p_cur != '\0') {
                    ljsonc_element *p_elem = precess_element(&p_cur);
                    
                    if (p_elem) {
                        json_append(json, p_elem);
                    }
                    
                    const char *p = strchr(p_cur, ',');
                    
                    if (p) {
                        p_cur = ++p;
                    }
                    else {
                        p = strchr(p_cur, '}');
                        
                        if (p) {
                            p_cur = p;
                        }
                        else {
                            error_parse = 1;
                            break;
                        }
                    }
                }
            }
        }
    }
        
    return json;
}
                    
ljsonc_json *parse_from_file(const char* filename)
{
    ljsonc_json *json = NULL;
    str_buffer *file_buf = create_buffer(8192);//8KB
    char line_buf[512];
    
    FILE *fp = fopen(filename, "r");
    
    if (fp) {
        
        while (fgets(line_buf, 512, fp)) {
        
            if (buffer_capacity(file_buf) - buffer_size(file_buf) < 512) {
                if (!buffer_increase(file_buf, 2048)) {
                    error_parse = 1;
                    destory_json(json);
                    return NULL;
                }
            }
            
            tidy_string(get_buffer_end(file_buf), line_buf);
            update_buffer(file_buf);
        }
        
        return parse_from_string(get_buffer_value(file_buf));
    }
    
    error_parse = 1;
    
    return json;
}

static
ljsonc_element *precess_element(const char **addr_str)
{
    ljsonc_element *elem = NULL;
    const char *p_cur = *addr_str;
    
    if (p_cur) {
        elem = create_element();
        p_cur = strchr(p_cur, '"');
        CString key = precess_string(&p_cur);
        
        if (*key != '\0') {
            p_cur = strchr(p_cur, ':');
            ++p_cur;
            
            if (p_cur) {
                ljsonc_value *value = precess_value(&p_cur);
                
                if (value) {
                    set_element(elem, key, value);
                    *addr_str = p_cur;
                    
                    return elem;
                }
            }
        }
    }
    
    error_parse = 1;  
    *addr_str = p_cur;
    return elem;
}

static
ljsonc_json *precess_json(const char **addr_str)
{
    ljsonc_json *json = create_json();
    const char *p_cur = *addr_str;
    const char *q = strchr(*addr_str, '}');
    
    if (json) {
        ++p_cur;
        while (*p_cur != '}' && *p_cur != '\0') {
            ljsonc_element *p_elem = precess_element(&p_cur);
    
            if (p_elem) {
                json_append(json, p_elem);
            }
                    
            const char *p = strchr(p_cur, ',');
                    
            if (p && p < q) {
                p_cur = ++p;
            }
            else {
                p = strchr(p_cur, '}');
                        
                if (p) {
                    p_cur = p;
                }
                else {
                    error_parse = 1;
                    break;
                }
            }
        }
    }
    
    *addr_str = ++p_cur;
    return json;
}

static
ljsonc_array *precess_array(const char **addr_str)
{
    ljsonc_array *array = create_array();
    const char *p_cur = *addr_str;
    
    while (*p_cur != ']') {
        ++p_cur;
        ljsonc_value *value = precess_value(&p_cur);
        
        if (value && !error_parse) {
            array_append(array, value);
            
            while (isspace(*p_cur)) {
                ++p_cur;
            }
            
            if (*p_cur == ',') {
                continue;
            }
            else {
                error_parse = 1;
                break;
            }
        }
    }
    
    *addr_str = ++p_cur;
    return array;    
}

static
CString precess_string(const char **addr_str)
{
    char *str = NULL;
    const char *p_cur = *addr_str;
    const char *end = strchr(p_cur + 1, '"');
    
    if (end) {
        int len = end - p_cur;
        str = (char *)malloc(sizeof(char)*len);
        
        if (str) {
            memcpy(str, p_cur+1, sizeof(char)*len);
            str[len -1] = '\0';
            *addr_str = ++end;
        }
    }
    else {
        error_parse = 1;
    }
    
    return str;
}
    
static
ljsonc_value *precess_value(const char **addr_str)
{
    ljsonc_value *value = NULL;
    const char *p_cur = *addr_str;
    
    if (p_cur) {
        while (*p_cur != '0' && isspace(*p_cur)) {
            ++p_cur;
        }
        
        value = create_value();
        
        if (value) {
        
            if (isdigit(*p_cur)) {
                char *dp, *lp;
                double vd;
                long vl;
                vd = strtod(p_cur, &dp);
                vl = strtol(p_cur, &lp, 10);
                
                if (lp == dp) {
                    value->valuetype = CJSON_VALUE_INT;
                    value->value.int_value = (int)vl;
                    *addr_str = lp;
                }
                else {
                    value->valuetype = CJSON_VALUE_DOUBLE;
                    value->value.df_value = vd;
                    *addr_str = dp;
                }
            }
            else {
            
                switch (*p_cur) {
                    case '"': {
                        char *str = precess_string(&p_cur);
                        
                        if (str) {
                            value->valuetype = CJSON_VALUE_STRING;
                            value->value.str_value = str;
                            *addr_str = p_cur;
                        }
                        else {
                            error_parse = 1;
                        }
                        break;
                    }
                    
                    case '{': {
                        ljsonc_json *json = precess_json(&p_cur);
                        
                        if (json) {
                            value->valuetype = CJSON_VALUE_JSON;
                            value->value.json_value = json;
                            *addr_str = p_cur;
                        }
                        else {
                            error_parse = 1;
                        }
                        break;   
                    }
                        
                    case '[': {
                        ljsonc_array *array = precess_array(&p_cur);
                        
                        if (array) {
                            value->valuetype = CJSON_VALUE_ARRAY;
                            value->value.array_value = array;
                            *addr_str = p_cur;
                        }
                        else {
                            error_parse = 1;
                        }
                        break;
                    }
                        
                    default:
                        error_parse = 1;
                        break;                    
                }
            }
        }
    }
    
    return value;
}

static void tidy_string(char *des, const char *src)
{
    while (src && *src != '\0') {
        if (isspace(*src)) {
            ++src;
        }
        else {
            *des++ = *src++;
        }
    }
}